/*
 * PathNormal.java
 *
 * Created on November 18, 2007, 11:50 AM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.wave;

import hextd.*;
import hextd.util.Context;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.util.Vector;

/**
 *
 * @author hylke
 */
public class PathNormal implements Path {

    private Context context;
    private Vector<Integer> stepXv;
    private Vector<Integer> stepYv;
    private int[] stepsX;
    private int[] stepsY;
    private Color blipColor = new Color(1.0f, 0.7f, 0.7f, 1f);
    private Color blipBackColor = new Color(1.0f, 0.7f, 0.7f, 0f);
    private Stroke stroke;
    private Shape blipShape;
    private Paint blipFill;
    private float blipSize;
    private BufferedImage[] blipImage;
    private int[][] blipStep;
    private int blipStepDelta = 2;
    AffineTransform blipTranslate;
    private int stepsPerSegment = 2;
    private int timePerStep = 40;
    private int localTime = 0;
    private int stepTime = 0;
    private boolean selected = false;
    private float baseDelay;

    /**
     * Creates a new instance of PathNormal
     */
    public PathNormal(Context context, float baseDelay) {
        this.context = context;
        this.stepXv = new Vector<Integer>();
        this.stepYv = new Vector<Integer>();
        this.blipTranslate = new AffineTransform();
        this.baseDelay = baseDelay;
    }

    public float getBaseDelay() {
        return this.baseDelay;
    }

    public void setBaseDelay(float d) {
        this.baseDelay = d;
    }

    public void addStep(int x, int y) {
        this.stepXv.add(x);
        this.stepYv.add(y);
    }

    public int length() {
        return this.stepsX.length;
    }

    public int[] getStepHexCoords(int step) {
        int[] retval = new int[2];
        retval[0] = this.stepsX[step];
        retval[1] = this.stepsY[step];
        return retval;
    }

    public float[] getStepPaint(int step) {
        float[] loc = this.getStep(step);
        loc[0] *= this.context.paintScale;
        loc[1] *= this.context.paintScale;
        loc[0] += this.context.offsetX;
        loc[1] += this.context.offsetY;
        return loc;
    }

    public float[] getStep(int step) {
        if (this.stepsX.length == 0) {
            float[] retval = {0f, 0f};
            return retval;
        } else {
            if (step < 0) {
                return this.getPreStep();
            }
            if (step >= this.stepsX.length) {
                return this.getPostStep();
            }
            float[] returnVals = new float[2];
            int x = stepsX[step];
            int y = stepsY[step];
            if (x < 0 || y < 0 || x >= context.gridWidth || y >= context.gridHeight || context.grid[x][y] == null) {
                // TODO some maps need a delay for timing. This is now done by adding invalid points at the start.
                // Until proper delay code is added, this warning is disabled.
                //System.err.println("ERROR, nonexistant point requested: "+x+","+y);
                float[] retval = {0f, 0f};
                return retval;
            }
            return context.grid[x][y].getFloatLocation();
        }
    }

    private float[] getPreStep() {
        int dx = 0;
        int dy = 0;
        float[] retVal = new float[2];
        if (this.stepsX[0] == 0) {
            dx = -2;
        }
        if (this.stepsX[0] == this.context.gridWidth - 1 - (this.stepsY[0] & 1)) {
            dx = 2;
        }
        if (this.stepsY[0] == 0) {
            dy = -2;
        }
        if (this.stepsY[0] == this.context.gridHeight - 1) {
            dy = 2;
        }
        retVal = this.getStep(0);
        retVal[0] += dx;
        retVal[1] += dy;
        return retVal;
    }

    private float[] getPostStep() {
        int count = this.stepsX.length - 1;
        int dx = 0;
        int dy = 0;
        float[] retVal = new float[2];
        if (this.stepsX[count] == 0) {
            dx = -2;
        }
        if (this.stepsX[count] == this.context.gridWidth - 1 - (this.stepsY[count] & 1)) {
            dx = 2;
        }
        if (this.stepsY[count] == 0) {
            dy = -2;
        }
        if (this.stepsY[count] == this.context.gridHeight - 1) {
            dy = 2;
        }
        retVal = this.getStep(count);
        retVal[0] += dx;
        retVal[1] += dy;
        return retVal;
    }

    public void insertStep(int i, int x, int y) {
        this.stepXv.add(i, x);
        this.stepYv.add(i, y);
        this.finalise();
    }

    public void updateStep(int i, int x, int y) {
        this.stepXv.remove(i);
        this.stepYv.remove(i);
        this.stepXv.add(i, x);
        this.stepYv.add(i, y);
        this.finalise();
    }

    public void removeStep(int i) {
        this.stepXv.remove(i);
        this.stepYv.remove(i);
        this.finalise();
    }

    public void finalise() {
        this.stepsX = new int[this.stepXv.size()];
        this.stepsY = new int[this.stepYv.size()];
        for (int i = 0; i < this.stepsX.length; i++) {
            this.stepsX[i] = this.stepXv.get(i);
            this.stepsY[i] = this.stepYv.get(i);
        }

        int blipCount = 1 + this.length() / this.blipStepDelta;
        this.blipStep = new int[blipCount][4];

        for (int i = 0; i < this.blipStep.length; i++) {
            this.blipStep[i][0] = Math.round(1.0f * i * this.blipStepDelta);
            this.blipStep[i][1] = 0;
            this.blipStep[i][2] = this.timePerStep - 1 - i % this.timePerStep;
            this.blipStep[i][3] = 0;
        }

    }

    public void paint(Graphics2D g2, int gameTime) {
        if (this.stepsX.length == 0) {
        // Don't try to paint an empty path...
        } else {
            float paintScale = this.context.paintScale;
            Stroke defaultStroke = g2.getStroke();

            if (this.context.gameState == HexTD.gameState.editing && this.selected) {
                Shape circle;
                g2.setColor(this.blipColor);
                g2.setStroke(this.stroke);
                GeneralPath poly1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD, this.stepsX.length);
                float[] point = this.getStepPaint(-1);
                poly1.moveTo(point[0], point[1]);
                for (int index = 0; index <= this.stepsX.length; index++) {
                    point = this.getStepPaint(index);
                    poly1.lineTo(point[0], point[1]);
                    circle = new Ellipse2D.Float(point[0] - this.blipSize / 2, point[1] - this.blipSize / 2, this.blipSize, this.blipSize);
                    g2.draw(circle);
                }
                g2.draw(poly1);

            } else {

                int correction = 0;
                if (this.blipStep[1][0] > blipStepDelta && this.blipStep[1][3] == 0) {
                    correction = blipStepDelta;
                    for (int i = this.blipStep.length - 1; i >= 0; i--) {
                        this.blipStep[i][3] -= correction;
                    }
                }

                for (int i = 0; i < this.blipStep.length; i++) {
                    this.blipStep[i][2]++;
                    if (this.blipStep[i][2] == this.blipImage.length) {
                        this.blipStep[i][2] = 0;
                        this.blipStep[i][0] += this.blipStep[i][3];
                        this.blipStep[i][3] = 0;
                        this.blipStep[i][1]++;
                        if (this.blipStep[i][1] >= this.stepsPerSegment) {
                            this.blipStep[i][1] = 0;
                            this.blipStep[i][0] += 1;
                            this.blipStep[i][3] = 0;
                        }
                    }
                    float[] point1 = this.getStepPaint(this.blipStep[i][0]);
                    float[] point2 = this.getStepPaint(this.blipStep[i][0] + 1);
                    float x, y;
                    int image = this.blipStep[i][2];
                    x = point1[0] - this.blipSize / 2 + (point2[0] - point1[0]) * this.blipStep[i][1] / this.stepsPerSegment;
                    y = point1[1] - this.blipSize / 2 + (point2[1] - point1[1]) * this.blipStep[i][1] / this.stepsPerSegment;
                    g2.drawImage(this.blipImage[image], (int) x, (int) y, null);
                }
                g2.setStroke(defaultStroke);
            }
        }
    }

    public void resetScale() {
        if (this.blipImage != null) {
            for (int i = 0; i < this.blipImage.length; i++) {
                if (this.blipImage[i] != null) {
                    this.blipImage[i].flush();
                }
            }
        } else {
            this.blipImage = new BufferedImage[this.timePerStep];
        }

        this.stroke = new BasicStroke(1.0f * this.context.strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);

        this.blipSize = this.context.paintScale / 4f;
        this.blipShape = new Ellipse2D.Float(0, 0, this.blipSize, this.blipSize);
        float[] dist = {0.0f, 1f};
        int num = this.blipImage.length;
        for (int i = 0; i < num; i++) {
            int trans = Math.max(0, 255 - i * 500 / num);
            Color[] colors = {new Color(this.blipColor.getRed(), this.blipColor.getGreen(), this.blipColor.getBlue(), trans), this.blipBackColor};
            this.blipFill = new RadialGradientPaint(this.blipSize / 2, this.blipSize / 2, this.blipSize / 2.1f, dist, colors);

            this.blipImage[i] = new BufferedImage(Math.round(this.blipSize), Math.round(this.blipSize), BufferedImage.TYPE_INT_ARGB_PRE);
            Graphics2D g2 = this.blipImage[i].createGraphics();
            g2.setPaint(blipFill);
            g2.fill(blipShape);
            g2.dispose();
        }
    }

    private void shiftUpDown(int to, int correction) {
        Integer x, y, shift;
        for (int i = 0; i < this.stepsY.length; i++) {
            //this.stepsY[i] += to;
            y = this.stepYv.remove(i);
            x = this.stepXv.remove(i);

            shift = 0;
            boolean odd = ((y & 1) == 1);
            if (correction < 0) {
                odd = !odd;
            }
            if (odd) {
                shift = correction;
            }

            y += to;
            x += shift;
            this.stepYv.add(i, y);
            this.stepXv.add(i, x);

        }
        this.finalise();
    }

    private void shiftLeftRight(int to) {
        Integer y;
        for (int i = 0; i < this.stepsX.length; i++) {
            //this.stepsX[i] += to;
            y = this.stepXv.remove(i);
            y += to;
            this.stepXv.add(i, y);
        }
        this.finalise();
    }

    public void shift(HexTD.direction direction, int amount, int correction) {
        switch (direction) {
            case UP:
            case NORTH:
                this.shiftUpDown(-amount, correction);
                break;
            case DOWN:
            case SOUTH:
                this.shiftUpDown(amount, correction);
                break;
            case RIGHT:
            case EAST:
                this.shiftLeftRight(amount);
                break;
            case LEFT:
            case WEST:
                this.shiftLeftRight(-amount);
                break;
        }
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }
}
